#include <signal.h>
#include <stdio.h>
#include <boost/foreach.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/chrono/thread_clock.hpp>
#include "Analyzer.h"
#include "Serializer.h"
#include "../db/Archive.h"
#include "../config/Config.h"
#include "Queues.h"
#include "../filter/Filter.h"
#include "../parser/MessageParser.h"
#include "../system/Observers.h"

#define foreach BOOST_FOREACH
//#define DEBUG

using namespace kc_sf;

// -------------------------------------------------------- //

Analyzer::Analyzer()
{
	a_is_working = false;
}

// -------------------------------------------------------- //

Analyzer::~Analyzer()
{
	a_analyzer_threads.join_all();
}

// -------------------------------------------------------- //

void Analyzer::start()
{
	a_is_working = true;
	while( a_is_working )
	{
		RawMessage rmsg = InputMessageQueue.waitAndPop();

        a_logger << info  << analyzer << mid( rmsg.Id )
        		<< "Pobrano wiadomość z kolejki wiadomości przychodzących" << flush;

		boost::thread *analyzer_thread = new boost::thread( &Analyzer::analyze, this, rmsg );
		a_analyzer_threads.add_thread( analyzer_thread );
	}
}

// -------------------------------------------------------- //

void Analyzer::stop()
{
	a_is_working = false;
	a_analyzer_threads.interrupt_all ();
	a_logger << info << analyzer << "Zakończono analizę wiadomości" << flush;
}

// -------------------------------------------------------- //

void Analyzer::analyze( RawMessage &raw_message )
{
	// block signals
    sigset_t signal_set;
    sigemptyset(&signal_set);
    sigaddset(&signal_set, SIGINT);
    sigaddset(&signal_set, SIGTERM);
    sigaddset(&signal_set, SIGHUP);
    sigaddset(&signal_set, SIGPIPE); // http://www.unixguide.net/network/socketfaq/2.19.shtml
    pthread_sigmask(SIG_BLOCK, &signal_set, NULL);

    using namespace boost::chrono;
#ifdef DEBUG
    thread_clock::time_point start = thread_clock::now();
#endif // DEBUG

	Config &cfg = Config::getInstance();
	bool create_tmp_files = cfg.cMode == Safe ? true : false;

	// zapis w pliku tymczasowym
	std::string filepath;
	if( create_tmp_files )
	{
		Serializer serializer( cfg.getPathToArchive(), raw_message );
		filepath = serializer();

	    a_logger << info  << analyzer << mid( raw_message.Id )
	    		<< " Zapamiętano wiadomość w pliku tymczasowym" << flush;
	}

	// parsowanie
	MessageParser parser( raw_message );
	Message message( raw_message );
	a_logger << info << analyzer << mid( raw_message.Id )
			<< "Parser rozpoczął przetwarzanie wiadomości" << flush;
	if( !parser.parse( message ) )
	{
		Serializer serializer( cfg.getPathToQuarantine(), raw_message );
		std::string quarantine_filepath = serializer();
		raw_message.rmFilepath = quarantine_filepath;

		a_logger << error << analyzer << mid( raw_message.Id )
				<< "Nie udało się sparsować wiadomosci. " << flush;
		a_logger << info << analyzer << mid( raw_message.Id )
				<< "Wiadomosć niezgodna ze standardami RFC została zapisana w kwarantannie: "
				<< quarantine_filepath << flush;
		return;
	}

    a_logger << info  << analyzer << mid( raw_message.Id )
    		<< "Parser zakończył działanie sukcesem." << newLine
    		<< "Typ wiadomości: " << message.mContentType.toString()
    		<< "; ilość załączników: " << message.mAttachments.size()
    		<< flush;

	// filtrowanie
	Filter filter;
	a_logger << info << analyzer << mid( raw_message.Id )
			<< "Filtr rozpoczął przetwarzanie wiadomości" << flush;
	int matches_nr;
	ActionType action = filter( message, matches_nr );

	std::string matches_str = "";
    switch( matches_nr )
    {
    	case 0:
    		matches_str = "Nie dopasowano żadnej reguły";
    		break;
    	case 1:
    		matches_str = "Dopasowano 1 regułę";
    	    break;
    	case 2:
    	case 3:
    	case 4:
    		matches_str = "Dopasowano " + boost::lexical_cast<std::string>( matches_nr ) + " reguły";
		    break;
		default:
			matches_str = "Dopasowano " + boost::lexical_cast<std::string>( matches_nr ) + " reguł";
		    break;
    }
    a_logger << info  << analyzer << mid( raw_message.Id )
    		<< "Filtr zakończył działanie sukcesem. " << newLine
    		<< matches_str << "." << newLine
    		<< "Wiadomość zaklasyfikowano do grupy \""
    		<< ActionTypeToString( action ) << "\"" << flush;

	// TODO
	//if( cfg.cCheckAntivir )
	//	checkAntivir( raw_message );

	// akcja
	if( create_tmp_files )
	{
		remove( filepath.c_str() );

	    a_logger << info  << analyzer << mid( raw_message.Id )
	    		<< " Usunięto pliki tymczasowe" << flush;
	}

	switch( action )
	{
		case Quarantine:
			{
				Serializer serializer( cfg.getPathToQuarantine(), raw_message );
				filepath = serializer();
				message.mRawMessage.rmFilepath = filepath;
				QuarantineObserver.add();

			    a_logger << info  << analyzer << mid( raw_message.Id )
			    		<< "Umieszczono wiadomość w kwarantannie" << flush;
			}
			break;
		case Pass:
		{
			a_logger << info  << analyzer << mid( raw_message.Id )
		    		<< "Nowa wiadomość w kolejce wiadomości wychodzących" << flush;
			OutputMessageQueue.push( TryRawMessage( raw_message ) );
			OutputMessageQueueObserver.add();
			SentObserver.add();
		}
			break;
		case Forward:
		{
			Address redirect_address = Config::getInstance().cRedirectAddress;
			raw_message.rmRcptsTo.clear();
			raw_message.rmRcptsTo.push_back( redirect_address );
			a_logger << info  << analyzer << mid( raw_message.Id )
		    		<< "Nowa wiadomość w kolejce wiadomości wychodzących" << flush;
			OutputMessageQueue.push( TryRawMessage( raw_message ) );
			OutputMessageQueueObserver.add();
			RedirectObserver.add();
		}
			break;
		case Delete:
			DeletedObserver.add();
		    a_logger << info  << analyzer << mid( raw_message.Id )
		    	<< "Usunięto wiadomość" << flush;
		    break;
		default:
			break;
	};

	// zapis w bazie danych
	if( create_tmp_files )
	{
		Archive archiver( message, action );
		archiver ();
	}

#ifdef DEBUG
    thread_clock::time_point stop = thread_clock::now();
    std::cout << "---> Analyzer: " << duration_cast<milliseconds>(stop - start).count() << " ms" << std::endl << std::endl;
#endif // DEBUG
}
